﻿//
// *************************************************************************************
// WP-Framework
// 
// Developed by
// Lukas Kretschmar (lkretschmar.dev@bluewin.ch)
//
// Switzerland, Copyright (c) 2012 lkretschmar
// This content is released under the MIT License (http://opensource.org/licenses/MIT).
//     
// *************************************************************************************
//
//


using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace KretschIT.WP_Fx.EasyMoq.Generator.Items
{
    internal class ClassAnalyzer : IClassAnalyzer
    {
        #region Events

        public IEnumerable<EventInfo> GetEventsOf(Type type)
        {
            return this.Collect<EventInfo>(type, t => t.GetEvents(), this.ContainsEvent);
        }

        #region Compare

        private bool ContainsEvent(IEnumerable<EventInfo> events, EventInfo eventInfo)
        {
            return events.Any(e => this.AreEqual(e, eventInfo));
        }

        private bool AreEqual(EventInfo e, EventInfo eventInfo)
        {
            return e.Name == eventInfo.Name && e.EventHandlerType == eventInfo.EventHandlerType;
        }

        #endregion

        #endregion

        #region Methods

        public IEnumerable<MethodInfo> GetMethodsOf(Type type)
        {
            return this.Collect<MethodInfo>(type, t => t.GetMethods().Where(m => !m.IsSpecialName), this.ContainsMethod);
        }

        #region Compare

        private bool ContainsMethod(IEnumerable<MethodInfo> methods, MethodInfo method)
        {
            return methods.Any(m => this.AreEqual(m, method));
        }

        private bool AreEqual(MethodInfo m, MethodInfo method)
        {
            return this.CompareMethods(m, method) && this.CompareArguments(m, method);
        }

        private bool CompareMethods(MethodInfo m, MethodInfo method)
        {
            return m.Name == method.Name && m.ReturnType == method.ReturnType;
        }

        private bool CompareArguments(MethodInfo m, MethodInfo method)
        {
            if (m.GetParameters().Count() != method.GetParameters().Count())
            {
                return false;
            }
            return this.CompareArgumenTypes(m, method);
        }

        private bool CompareArgumenTypes(MethodInfo m, MethodInfo method)
        {
            var methodParameters = method.GetParameters();
            var otherMethodParameters = m.GetParameters();
            for (int i = 0; i < methodParameters.Count(); i++)
            {
                if (methodParameters.ElementAt(i) != otherMethodParameters.ElementAt(i))
                {
                    return false;
                }
            }
            return true;
        }

        #endregion

        #endregion

        #region Properties

        public IEnumerable<PropertyInfo> GetPropertiesOf(Type type)
        {
            return this.Collect<PropertyInfo>(type, t => t.GetProperties(), this.ContainsProperty, this.AddPropertiesTo);
        }

        #region Compare

        private bool ContainsProperty(IEnumerable<PropertyInfo> properties, PropertyInfo property)
        {
            return properties.Any(p => this.AreEqual(p, property));
        }

        private bool AreEqual(PropertyInfo p, PropertyInfo property)
        {
            return this.CompareProperty(p, property)
                && this.ComparePropertyFunctionality(p, property) == PropertyCompareStates.AreEqual;
        }

        private bool CompareProperty(PropertyInfo p, PropertyInfo property)
        {
            return p.Name == property.Name
                            && p.PropertyType == property.PropertyType;
        }

        private PropertyCompareStates ComparePropertyFunctionality(PropertyInfo p, PropertyInfo property)
        {
            if (this.BothCanDoSame(p, property))
            {
                return PropertyCompareStates.AreEqual;
            }
            if (this.BothAreDifferent(p, property))
            {
                return PropertyCompareStates.PropertiesAreCompletelyDiffrent;
            }
            if (this.CanOnlyDoOneThing(p))
            {
                return PropertyCompareStates.NewCanDoMore;
            }
            return PropertyCompareStates.NewCanDoMore;
        }

        private bool BothCanDoSame(PropertyInfo p, PropertyInfo property)
        {
            return p.CanRead == property.CanRead
                && p.CanWrite == property.CanWrite;
        }
        
        private bool BothAreDifferent(PropertyInfo p, PropertyInfo property)
        {
            return p.CanRead != property.CanRead
                   && p.CanWrite != property.CanWrite;
        }

        private bool CanOnlyDoOneThing(PropertyInfo p)
        {
            return p.CanRead != p.CanWrite;
        }

        #endregion

        #region Add

        private void AddPropertiesTo(List<PropertyInfo> properties, IEnumerable<PropertyInfo> newProperties)
        {
            foreach (var newProperty in newProperties)
            {
                if (this.CanAdd(newProperty, properties))
                {
                    properties.Add(newProperty);
                }
            }
        }

        private bool CanAdd(PropertyInfo newProperty, List<PropertyInfo> properties)
        {
            var oldProperty = properties.SingleOrDefault(p => this.CompareProperty(p, newProperty));
            if (oldProperty != null)
            {
                var compare = this.ComparePropertyFunctionality(oldProperty, newProperty);
                if (compare == PropertyCompareStates.AreEqual || compare == PropertyCompareStates.OldCanDoMore)
                {
                    return false;
                }
                else if (compare == PropertyCompareStates.NewCanDoMore)
                {
                    properties.Remove(oldProperty);
                }                
            }
            return true;
        }

        #endregion

        #endregion

        private IEnumerable<T> Collect<T>(Type type, Func<Type, IEnumerable<T>> collectOf, Func<IEnumerable<T>, T, bool> isContainedIn, Action<List<T>, IEnumerable<T>> addTo = null)
        {
            var list = new List<T>();
            list.AddRange(collectOf(type));
            foreach (var i in type.GetInterfaces())
            {
                var newParts = this.FilterNew<T>(collectOf, isContainedIn, list, i);
                this.AddTo(list, newParts, addTo);
            }
            return list;
        }

        private IEnumerable<T> FilterNew<T>(Func<Type, IEnumerable<T>> collectOf, Func<IEnumerable<T>, T, bool> isContainedIn, List<T> list, Type i)
        {
            var parts = collectOf(i);
            return parts.Where(p => !isContainedIn(list, p));
        }

        private void AddTo<T>(List<T> list, IEnumerable<T> newParts, Action<List<T>, IEnumerable<T>> addTo)
        {
            if (addTo != null)
            {
                addTo(list, newParts);
            }
            else
            {
                list.AddRange(newParts);
            }
        }
    }
}
